﻿using OfficeOpenXml;
using System;
using System.IO;
using System.Text;
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
using HappyPlus.CommonUtils;

namespace HappyPlus.Editor
{
    /// <summary>
    /// 根据Excel内容生成Scriptableobject配置
    /// </summary>
    /// <remarks>
    /// 第一行：描述（注释）
    /// 第二行：数据类型
    /// 第三行：变量名
    /// </remarks>
    public class ConfigScriptGenerator
    {
        [MenuItem("Tools/Config/Generate Config")]
        private static void ReadDataFromConfig()
        {
            var fileName = Application.dataPath + "\\Config\\" + "MyFirstExcel.xlsx";
            var stream = new FileStream(fileName, FileMode.Open);
            using (var package = new ExcelPackage(stream))
            {
                var worksheets = package.Workbook.Worksheets;
                var sheetCount = worksheets.Count;
                for (var index = 0; index < sheetCount; ++index)
                {
                    var sheet = worksheets[index];
                    for (int i = sheet.Dimension.Start.Column, j = sheet.Dimension.End.Column; i <= j; ++i)
                    {
                        for (int m = sheet.Dimension.Start.Row, n = sheet.Dimension.End.Row; m <= n; ++m)
                        {
                            var str = sheet.GetValue(m, i).ToString();
                            Debug.Log(str);
                        }
                    }
                }
            }
            stream.Close();
            stream.Dispose();
        }

        private static List<string> collectionList = new List<string>();

        [MenuItem("Tools/Config/Read All Excel")]
        private static void ReadAllExcel()
        {
            collectionList.Clear();

            if (Directory.Exists(EditorConstant.EditorConfigDirectory))
            {
                var allFiles = Directory.GetFiles(EditorConstant.EditorConfigDirectory, "*.*");
                if (allFiles != null)
                {
                    foreach (var file in allFiles)
                    {
                        if (!string.IsNullOrEmpty(file) && (file.EndsWith(".xlsx") || file.EndsWith(".xls")))
                        {
                            GenerateClassFromConfig(file);
                            CreateAssetWithConfigData(file);
                        }
                    }
                }
            }

            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.AppendLine("using System.Collections.Generic;");
            stringBuilder.AppendLine("using UnityEngine;");
            stringBuilder.AppendLine();
            stringBuilder.AppendLine("namespace HappyPlus.DataConfig");
            stringBuilder.AppendLine("{");
            stringBuilder.AppendLine("\tpublic class DictData");
            stringBuilder.AppendLine("\t{");

            foreach(var item in collectionList)
            {
                stringBuilder.AppendLine($"\t\tprivate static Dictionary<int, {item}> {item}Dict;");
            }

            stringBuilder.AppendLine();
            stringBuilder.AppendLine("\t\tpublic static void Init()");
            stringBuilder.AppendLine("\t\t{");
            foreach(var item in collectionList)
            {
                var varName = item.Substring(0, 1).ToLower() + item.Substring(1);
                stringBuilder.AppendLine();
                stringBuilder.AppendLine($"\t\t\tvar {varName}Data = Resources.Load<{item}Collection>(\"DataAssets/{item}Collection\");");
                stringBuilder.AppendLine($"\t\t\tif ({varName}Data != null)");
                stringBuilder.AppendLine("\t\t\t{");
                stringBuilder.AppendLine($"\t\t\t\t{item}Dict = {varName}Data.LoadDataToMap();");
                stringBuilder.AppendLine("\t\t\t}");
            }
            stringBuilder.AppendLine("\t\t}");

            stringBuilder.AppendLine();
            foreach(var item in collectionList)
            {
                stringBuilder.AppendLine("\t\t/// <summary>");
                stringBuilder.AppendLine($"\t\t/// 根据ID获取{item}数据");
                stringBuilder.AppendLine("\t\t/// </summary>");
                stringBuilder.AppendLine($"\t\tpublic static bool Get{item}Data(int id, out {item} data)");
                stringBuilder.AppendLine("\t\t{");
                stringBuilder.AppendLine($"\t\t\treturn {item}Dict.TryGetValue(id, out data);");
                stringBuilder.AppendLine("\t\t}");
                stringBuilder.AppendLine();
            }

            stringBuilder.AppendLine("\t}");
            stringBuilder.AppendLine("}");

            var csharpFileName = "DictData.cs";
            var csharpFilePath = EditorConstant.ConfigClassDirectory + csharpFileName;
            var csharpFile = new FileInfo(csharpFilePath);

            if (csharpFile.Exists)
            {
                csharpFile.Delete();
                //csharpFile = new FileInfo(csharpFilePath);
            }

            using (var writeStream = new FileStream(csharpFilePath,
                                                    FileMode.OpenOrCreate,
                                                    FileAccess.ReadWrite))
            {
                var content = Encoding.UTF8.GetBytes(stringBuilder.ToString());
                writeStream.Write(content, 0, content.Length);
            }

            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
        }

        [MenuItem("Tools/Config/Read Single Excel")]
        private static void ReadSingleExcel()
        {
            var filters = new string[] { "xlsx", "xlsx", "xls", "xls", "*.*", "*" };
            var filePath =
                EditorUtility.OpenFilePanelWithFilters("选择Excel配置文件",
                                                       EditorConstant.EditorConfigDirectory,
                                                       filters);
            if(!string.IsNullOrEmpty(filePath))
            {
                collectionList.Clear();
                GenerateClassFromConfig(filePath);
                CreateAssetWithConfigData(filePath);
            }            
        }

        /// <summary>
        /// 根据Excel文件建立数据结构
        /// </summary>
        /// <param name="filePath"></param>
        private static void GenerateClassFromConfig(string filePath)
        {
            StringBuilder cshapFileBuilder = new StringBuilder();            

            var fileStream = new FileStream(filePath, FileMode.Open);
            try
            {
                using (var package = new ExcelPackage(fileStream))
                {
                    var worksheets = package.Workbook.Worksheets;
                    var sheetCount = worksheets.Count;                    

                    for (var index = 0; index < sheetCount; ++index)
                    {
                        var sheet = worksheets[index];
                        if (sheet.Dimension.Rows < 3)
                        {
                            Debug.LogWarning(sheet.Name + ": 少于3行，缺少必要的信息生成数据结构！");
                            continue;
                        }

                        cshapFileBuilder.Clear();
                        cshapFileBuilder.AppendLine("using System.Collections;");
                        cshapFileBuilder.AppendLine("using System.Collections.Generic;");
                        cshapFileBuilder.AppendLine("using UnityEngine;");
                        cshapFileBuilder.AppendLine("using System;");
                        cshapFileBuilder.AppendLine("\nnamespace HappyPlus.DataConfig");
                        cshapFileBuilder.AppendLine("{");

                        cshapFileBuilder.AppendLine("\t[Serializable]");
                        cshapFileBuilder.AppendLine($"\tpublic class {sheet.Name}Collection : ScriptableObject");
                        cshapFileBuilder.AppendLine("\t{");
                        cshapFileBuilder.AppendLine("\t\t[SerializeField]");
                        cshapFileBuilder.AppendLine($"\t\tpublic List<{sheet.Name}> {sheet.Name}List;");

                        cshapFileBuilder.AppendLine();
                        cshapFileBuilder.AppendLine($"\t\tpublic Dictionary<int, {sheet.Name}> LoadDataToMap()");
                        cshapFileBuilder.AppendLine("\t\t{");

                        cshapFileBuilder.AppendLine();
                        cshapFileBuilder.AppendLine($"\t\t\tif({sheet.Name}List == null)");
                        cshapFileBuilder.AppendLine("\t\t\t{");
                        cshapFileBuilder.AppendLine($"\t\t\t\treturn new Dictionary<int, {sheet.Name}>();");
                        cshapFileBuilder.AppendLine("\t\t\t}");

                        cshapFileBuilder.AppendLine();
                        cshapFileBuilder.AppendLine($"\t\t\tvar dataMap = new Dictionary<int, {sheet.Name}>();");
                        cshapFileBuilder.AppendLine($"\t\t\tforeach (var item in {sheet.Name}List)");
                        cshapFileBuilder.AppendLine("\t\t\t{");
                        cshapFileBuilder.AppendLine("\t\t\t\tdataMap.Add(item.ID, item);");
                        cshapFileBuilder.AppendLine("\t\t\t}");
                        cshapFileBuilder.AppendLine("\t\t\treturn dataMap;");

                        cshapFileBuilder.AppendLine("\t\t}");

                        cshapFileBuilder.AppendLine("\t}");
                        cshapFileBuilder.AppendLine();

                        cshapFileBuilder.AppendLine("\t[Serializable]");
                        cshapFileBuilder.AppendLine("\tpublic class " + sheet.Name);
                        cshapFileBuilder.AppendLine("\t{");

                        // 第一行为注释
                        // 第二行为数据类型
                        // 第三行为变量名
                        for (int i = sheet.Dimension.Start.Column, j = sheet.Dimension.End.Column; i <= j; ++i)
                        {
                            cshapFileBuilder.AppendLine();
                            cshapFileBuilder.AppendLine("\t\t/// <summary>");
                            cshapFileBuilder.AppendLine("\t\t/// " + sheet.GetValue(1, i).ToString());
                            cshapFileBuilder.AppendLine("\t\t/// </summary>");
                            cshapFileBuilder.AppendLine("\t\t[SerializeField]");
                            cshapFileBuilder.AppendFormat("\t\tpublic {0} {1};",
                                                          TypeUtils.CheckBaseType(sheet.GetValue(2, i).ToString()),
                                                          sheet.GetValue(3, i).ToString());
                            cshapFileBuilder.AppendLine();
                        }
                        cshapFileBuilder.AppendLine("\n\t}");

                        cshapFileBuilder.AppendLine("}");
                        cshapFileBuilder.AppendLine();

                        if (!Directory.Exists(EditorConstant.ConfigClassDirectory))
                        {
                            Directory.CreateDirectory(EditorConstant.ConfigClassDirectory);
                        }

                        var csharpFileName = sheet.Name + "Collection" + ".cs";
                        var csharpFilePath = EditorConstant.ConfigClassDirectory + csharpFileName;
                        var csharpFile = new FileInfo(csharpFilePath);

                        if (csharpFile.Exists)
                        {
                            csharpFile.Delete();
                            csharpFile = new FileInfo(csharpFilePath);
                        }

                        using (var writeStream = new FileStream(csharpFilePath,
                                                                FileMode.OpenOrCreate,
                                                                FileAccess.ReadWrite))
                        {
                            var content = Encoding.UTF8.GetBytes(cshapFileBuilder.ToString());
                            writeStream.Write(content, 0, content.Length);
                        }

                        collectionList.Add(sheet.Name);
                    }                    
                }
            }
            finally
            {
                fileStream.Close();
                fileStream.Dispose();
            }

            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
        }

        /// <summary>
        /// 根据配置建立的数据结构生成.asset，并读取Excel中的数据填充到.asset文件中
        /// </summary>
        private static void CreateAssetWithConfigData(string filePath)
        {
            var dataStream = new FileStream(filePath, FileMode.Open);
            try
            {
                using (var package = new ExcelPackage(dataStream))
                {
                    var worksheets = package.Workbook.Worksheets;
                    var sheetCount = worksheets.Count;

                    for (var index = 0; index < sheetCount; ++index)
                    {
                        var sheet = worksheets[index];
                        if (sheet.Dimension.Rows < 4)
                        {
                            Debug.LogWarning(sheet.Name + ": 少于4行，没有足够的数据！");
                            continue;
                        }
                        
                        var typeName = "HappyPlus.DataConfig." + sheet.Name;
                        var type = TypeUtils.GetType(typeName);
                        if (type == null)
                        {
                            continue;
                        }

                        var modelList = Activator.CreateInstance(typeof(List<>).MakeGenericType(new Type[] { type }));
                        var addMethod = modelList.GetType().GetMethod("Add");
                        object model;
                        // 每一行为一个数据结构的数据
                        for (int m = 4, n = sheet.Dimension.End.Row; m <= n; ++m)
                        {
                            model = Activator.CreateInstance(type);
                            var modelType = model.GetType();
                            for (int i = sheet.Dimension.Start.Column, j = sheet.Dimension.End.Column; i <= j; ++i)
                            {
                                var typeStr = TypeUtils.CheckBaseType(sheet.GetValue(2, i).ToString());
                                if(string.IsNullOrEmpty(typeStr))
                                {
                                    continue;
                                }

                                if (typeStr.Contains("List"))
                                {
                                    var leftIndex = typeStr.IndexOf('<');
                                    var rightIndex = typeStr.IndexOf('>');
                                    var theType = typeStr.Substring(leftIndex + 1, rightIndex - leftIndex - 1);
                                    var baseType = TypeUtils.GetBaseType(theType);

                                    // List value
                                    var sheetValue = "";
                                    var sheetValueObj = sheet.GetValue(m, i);
                                    if(sheetValueObj != null)
                                    {
                                        sheetValue = sheetValueObj.ToString();
                                    }
                                    var val = sheetValue.Trim('[',']', ' ').Replace(" ", "");
                                    var valueList = val.Split('|');

                                    var memList = Activator.CreateInstance(typeof(List<>).MakeGenericType(new Type[] { baseType }));
                                    var memListAddMethod = memList.GetType().GetMethod("Add");
                                    var memLength = valueList != null ? valueList.Length : 0;
                                    for(int idx = 0; idx < memLength;++idx)
                                    {
                                        if (!string.IsNullOrEmpty(valueList[idx]) && !string.IsNullOrWhiteSpace(valueList[idx]))
                                        {
                                            var realValue = Convert.ChangeType(valueList[idx], baseType);
                                            memListAddMethod.Invoke(memList, new object[] { realValue });
                                        }
                                    }
                                    var memberName = sheet.GetValue(3, i).ToString();
                                    var member = modelType.GetField(memberName);
                                    member.SetValue(model, memList);
                                }
                                else
                                {
                                    var val = sheet.GetValue(m, i);
                                    var baseType = TypeUtils.GetBaseType(typeStr);

                                    var memValue = Convert.ChangeType(val, baseType);

                                    var memberName = sheet.GetValue(3, i).ToString();
                                    var member = modelType.GetField(memberName);
                                    member.SetValue(model, memValue);
                                }
                            }
                            addMethod.Invoke(modelList, new object[] { model });
                        }

                        var collectionClassName = "HappyPlus.DataConfig." + sheet.Name + "Collection";
                        var objType = TypeUtils.GetType(collectionClassName);
                        if (objType != null)
                        {
                            var assetData = ScriptableObject.CreateInstance(objType);
                            var assetDataType = assetData.GetType();
                            var listMember = assetDataType.GetField(sheet.Name + "List");
                            if (listMember != null)
                            {
                                listMember.SetValue(assetData, modelList);
                            }

                            var assetPath = EditorConstant.DataAssetsDirectory + sheet.Name + "Collection.asset";
                            AssetDatabase.CreateAsset(assetData, assetPath);
                            AssetDatabase.SaveAssets();
                            AssetDatabase.Refresh();
                        }
                    }
                }
            }
            finally
            {
                dataStream.Close();
                dataStream.Dispose();
            }
        }

        [MenuItem("Tools/Config/Read Constant Excel")]
        private static void ReadConstantExcel()
        {
            if (Directory.Exists(EditorConstant.EditorConfigConstantDirectory))
            {
                var allFiles = Directory.GetFiles(EditorConstant.EditorConfigConstantDirectory, "*.*");
                if (allFiles != null)
                {
                    foreach (var file in allFiles)
                    {
                        if (!string.IsNullOrEmpty(file) && (file.EndsWith(".xlsx") || file.EndsWith(".xls")))
                        {
                            GenerateConstantFile(file);
                            //CreateAssetWithConfigData(file);
                        }
                    }
                }
            }
        }

        private static void GenerateConstantFile(string filePath)
        {
            StringBuilder cshapFileBuilder = new StringBuilder();
            StringBuilder fieldStringBuilder = new StringBuilder();
            StringBuilder propertyStringBuilder = new StringBuilder();

            var fileStream = new FileStream(filePath, FileMode.Open);
            try
            {
                using (var package = new ExcelPackage(fileStream))
                {
                    var worksheets = package.Workbook.Worksheets;
                    var sheetCount = worksheets.Count;

                    for (var index = 0; index < sheetCount; ++index)
                    {
                        var sheet = worksheets[index];
                        if (sheet.Dimension.Rows < 1)
                        {
                            Debug.LogWarning(sheet.Name + ": 少于1行，缺少必要的信息生成数据结构！");
                            continue;
                        }

                        cshapFileBuilder.Clear();
                        cshapFileBuilder.AppendLine("using System.Collections;");
                        cshapFileBuilder.AppendLine("using System.Collections.Generic;");
                        cshapFileBuilder.AppendLine("using UnityEngine;");
                        cshapFileBuilder.AppendLine("using System;");
                        cshapFileBuilder.AppendLine("\nnamespace HappyPlus.DataConfig");
                        cshapFileBuilder.AppendLine("{");

                        cshapFileBuilder.AppendLine("\tpublic class " + sheet.Name);
                        cshapFileBuilder.AppendLine("\t{");

                        // 第一行为说明列的内容，实际内容从第二行开始
                        // 第一列为数据类型
                        // 第二列为变量名
                        // 第三列为变量值
                        // 第四列为注释
                        fieldStringBuilder.Clear();
                        propertyStringBuilder.Clear();
                        for (int m = 2, n = sheet.Dimension.End.Row; m <= n; ++m)
                        {
                            string memType = TypeUtils.CheckBaseType(sheet.GetValue(m, 1).ToString());
                            if (string.IsNullOrEmpty(memType))
                            {
                                Debug.LogError($"配置在[{m},{n}]处类型错误");
                                continue;
                            }

                            propertyStringBuilder.AppendLine();
                            propertyStringBuilder.AppendLine("\t\t/// <summary>");
                            propertyStringBuilder.AppendLine("\t\t/// " + sheet.GetValue(m, 4).ToString());
                            propertyStringBuilder.AppendLine("\t\t/// </summary>");
                            propertyStringBuilder.AppendFormat("\t\tpublic static {0} {1}", memType, sheet.GetValue(m, 2).ToString());
                            propertyStringBuilder.AppendLine();
                            propertyStringBuilder.AppendLine("\t\t{");
                            propertyStringBuilder.AppendLine($"\t\t\tget {{ return m{sheet.GetValue(m, 2)}; }}");
                            propertyStringBuilder.AppendLine("\t\t}");

                            fieldStringBuilder.AppendLine();
                            if (memType.StartsWith("List"))
                            {
                                var leftIndex = memType.IndexOf('<');
                                var rightIndex = memType.IndexOf('>');
                                var theType = memType.Substring(leftIndex + 1, rightIndex - leftIndex - 1);
                                var baseType = TypeUtils.GetBaseType(theType);

                                var valObj = sheet.GetValue(m, 3);
                                var valStr = valObj != null ? valObj.ToString().Trim('[', ']').Replace(" ", "") : "";
                                if (!string.IsNullOrEmpty(valStr) && !string.IsNullOrWhiteSpace(valStr))
                                {
                                    var str = $"new List<{baseType}> () {{{valStr.Replace('|', ',')}}}";
                                    fieldStringBuilder.AppendFormat("\t\tprivate static readonly {0} m{1} = {2};", memType, sheet.GetValue(m, 2).ToString(), str);
                                }
                                else
                                {
                                    fieldStringBuilder.AppendFormat("\t\tprivate static readonly {0} m{1} = {2};", memType, sheet.GetValue(m, 2).ToString(), $"new List<{baseType}> () {{}}");
                                }
                            }
                            else
                            {
                                fieldStringBuilder.AppendFormat("\t\tprivate static readonly {0} m{1} = {2};", memType, sheet.GetValue(m, 2).ToString(), sheet.GetValue(m, 3).ToString());
                            }
                        }

                        cshapFileBuilder.AppendLine(fieldStringBuilder.ToString());
                        cshapFileBuilder.AppendLine(propertyStringBuilder.ToString());
                        cshapFileBuilder.AppendLine("\n\t}");

                        cshapFileBuilder.AppendLine("}");
                        cshapFileBuilder.AppendLine();

                        if (!Directory.Exists(EditorConstant.ConfigClassDirectory))
                        {
                            Directory.CreateDirectory(EditorConstant.ConfigClassDirectory);
                        }

                        var csharpFileName = "DictData" + sheet.Name + ".cs";
                        var csharpFilePath = EditorConstant.ConfigClassDirectory + csharpFileName;
                        var csharpFile = new FileInfo(csharpFilePath);

                        if (csharpFile.Exists)
                        {
                            csharpFile.Delete();
                            csharpFile = new FileInfo(csharpFilePath);
                        }

                        using (var writeStream = new FileStream(csharpFilePath,
                                                                FileMode.OpenOrCreate,
                                                                FileAccess.ReadWrite))
                        {
                            var content = Encoding.UTF8.GetBytes(cshapFileBuilder.ToString());
                            writeStream.Write(content, 0, content.Length);
                        }
                    }
                }
            }
            finally
            {
                fileStream.Close();
                fileStream.Dispose();
            }

            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
        }
    }
}
